#include "reminder.h"
#include "reminderitem.h"
//#include "logger.h"

#include <QtDebug>



Reminder::Reminder(QObject *parent) : QObject(parent)
{
    id = QUuid::createUuid();
    timer = new QTimer(this);
    item = new ReminderItem(this);
    this->setState(Reminder::Stopped);

    this->setAudio(false);
    this->setTooltip(false);
    this->setDialog(false);
    this->setAutoclose(false);
    this->setLoadUrl(false);
    this->setAutodelete(false);
    this->setShutdown(false);
    this->setShutdownType(Reminder::LogOff);
    this->setExecute(false);
    this->setExecutePath(QString::null);
    this->setExecuteArgs(QString::null);
    this->setCheckMissed(false);

    /*QString log = QString("Construct reminder with id: %1")
            .arg(getId().toString())
            ;
    logger = new Logger(this);
    logger->writeLog(log);
    */

    connect(timer, SIGNAL(timeout()), this, SLOT(notify()));
}

Reminder::Reminder(QObject *parent, const QString& name) : QObject(parent)
{
    id = QUuid::createUuid();
    this->setName(name);

    timer = new QTimer(this);
    item = new ReminderItem(this);
    this->setState(Reminder::Stopped);

    this->setAudio(false);
    this->setTooltip(false);
    this->setDialog(false);
    this->setAutoclose(false);
    this->setLoadUrl(false);
    this->setAutodelete(false);
    this->setShutdown(false);
    this->setShutdownType(Reminder::LogOff);
    this->setExecute(false);
    this->setExecutePath(QString::null);
    this->setExecuteArgs(QString::null);
    this->setCheckMissed(false);

    /*QString log = QString("Construct reminder: %1, with id: %2")
            .arg(getName())
            .arg(getId().toString())
            ;

    logger = new Logger(this);
    logger->writeLog(log);
    */
    connect(timer, SIGNAL(timeout()), this, SLOT(notify()));
}

Reminder::Reminder(QObject *parent, int index, const QString &name) : QObject(parent)
{
    id = QUuid::createUuid();
    this->setIndex(index);

    this->setName(name);

    timer = new QTimer(this);
    item = new ReminderItem(this);
    this->setState(Reminder::Stopped);

    this->setAudio(false);
    this->setTooltip(false);
    this->setDialog(false);
    this->setAutoclose(false);
    this->setLoadUrl(false);
    this->setAutodelete(false);
    this->setShutdown(false);
    this->setShutdownType(Reminder::LogOff);
    this->setExecute(false);
    this->setExecutePath(QString::null);
    this->setExecuteArgs(QString::null);
    this->setCheckMissed(false);

    /*QString log = QString("Construct reminder: %1, with index: %2, with id: %3")
            .arg(getName())
            .arg(getIndex())
            .arg(getId().toString())
            ;

    logger = new Logger(this);
    logger->writeLog(log);
    */

    connect(timer, SIGNAL(timeout()), this, SLOT(notify()));
}

Reminder::~Reminder()
{
    if(timer)
        delete(timer);
    /*
    logger->writeLog(QString("Desrtruct reminder: %1, with index: %2, with id: %3")
                     .arg(getName())
                     .arg(getIndex())
                     .arg(getId().toString())
                     );
                     */
}

void Reminder::start()
{
    if(this->calculateScheduleTime()){
        /*logger->writeLog(QString("Reminder %1 %2 - next run datetime (msecs): %3")
                         .arg(getName())
                         .arg(getId().toString())
                         .arg(getNextRun().toMSecsSinceEpoch())
                         );
                         */
        int _timeout = QDateTime::currentDateTime().msecsTo(this->getNextRun());
        if(_timeout > 0 && _timeout < 2073600000 ){ //the timer have a 2073600000 msecs limit
            timer->setInterval(_timeout);
            timer->setSingleShot(true);
            timer->start();
        }
        else{
            //qDebug() << QString::number(_timeout) << " msecs- exceeding the timer limit. Waiting...";
            QTimer::singleShot(2073600000, this, SLOT(start()));
        }
        state = Reminder::Runned;
        emit changeState(this);
        setIcon();
    }
    else this->stop();
}

void Reminder::postpone(uint secs)
{
    this->stop();
    this->setNextRun(QDateTime::currentDateTime());
    this->increaseNextRun(secs);
    state = Reminder::Postponed;
    this->start();
}

void Reminder::stop()
{
    if(timer){
        if(timer->isActive()){
            timer->stop();
        }
    }
    state = Stopped;
    emit changeState(this);
    setIcon();
}

bool Reminder::isRunned() const{
    bool runned = false;
    switch(state){
    case Reminder::Postponed:
    case Reminder::Runned:
        runned = true;
        break;
    default: break;
    }
    return runned;
}

bool Reminder::calculateScheduleTime()
{
    bool schedule = false;
    if(state == Reminder::Postponed && (QDateTime::currentDateTime().secsTo(getNextRun())) > 0)
        return true;
    switch(this->type){
        case Reminder::Once : {
            if(!getNextRun().isNull()){
                nextRun = _start;
                if(QDateTime::currentDateTime().secsTo(this->getNextRun()) > 0)
                    schedule = true;
            }
            break;
        }
        case Reminder::Minutely : {
            if(!getNextRun().isNull() && (this->getMinutes() > 0)){
                nextRun = _start;
                while(QDateTime::currentDateTime().secsTo(this->getNextRun()) <= 0){
                    this->increaseNextRun(this->getMinutes()*60);
                }
                schedule = true;
            }
            break;
        }
        case Reminder::Hourly : {
            if(!getNextRun().isNull() && (this->getHours() > 0)){
                nextRun = _start;
                while(QDateTime::currentDateTime().secsTo(this->getNextRun()) <= 0){
                    this->increaseNextRun(this->getHours()*60*60);
                }
                schedule = true;
            }
            break;
        }
        case Reminder::Daily : {
            if(!getNextRun().isNull()){
                nextRun = _start;
                QDateTime next = QDateTime::currentDateTime();
                next.setTime(getNextRun().time());
                for(int n = 0; n < 8; n++){
                    if( days[next.date().dayOfWeek()-1] ){
                        if(QDateTime::currentDateTime().secsTo(next) > 0){
                            this->setNextRun(next);
                            /*
                               logger->writeLog(QString("Reminder %1 %2 calculated start date time (msecs): %3")
                                             .arg(getName())
                                             .arg(getId().toString())
                                             .arg(next.toMSecsSinceEpoch())
                                             );
                                             */
                            return true;
                        }
                    }
                    next = next.addDays(1);
                }
            }
            schedule = false;
            break;
        }
        case Reminder::Monthly : {
            if(!getNextRun().isNull()){
                nextRun = _start;
                while(QDateTime::currentDateTime().secsTo(this->getNextRun()) <= 0){
                    this->setNextRun(nextRun.addMonths(1));
                }
                schedule = true;
            }
            break;
        }
        case Reminder::MonthlyByDay : {
            if(!getNextRun().isNull()){
                nextRun = _start;
                while(QDateTime::currentDateTime().secsTo(this->getNextRun()) <= 0){
                    this->setNextRun(this->dayOfNextMonths(nextRun));
                }
                schedule = true;
            }
            break;
        }
        case Reminder::Yearly : {
            if(!getNextRun().isNull()){
                nextRun = _start;
                while(QDateTime::currentDateTime().secsTo(this->getNextRun()) <= 0){
                    this->setNextRun(nextRun.addMonths(12));
                }
                schedule = true;
            }
            break;
        }
        case Reminder::YearlyByDay : {
            if(!getNextRun().isNull()){
                nextRun = _start;
                while(QDateTime::currentDateTime().secsTo(this->getNextRun()) <= 0){
                    this->setNextRun(this->dayOfNextMonths(nextRun, 12));
                }
                schedule = true;
            }
        break;
        }
        case Reminder::Timeout : {
            this->setNextRun(QDateTime::currentDateTime().addSecs(getTimeout()));
            schedule = true;
            break;

        }
        default: break;
    }
    return schedule;
}

void Reminder::notify()
{
    if(QDateTime::currentDateTime().msecsTo(getNextRun()) > 0){
        QTimer::singleShot(QDateTime::currentDateTime().msecsTo(getNextRun()), this, SLOT(notify()));
        return;
    }
    this->stop();
    emit this->remind(this);
    /*
    logger->writeLog(QString("New reminder event: %1, with index: %2, with id: %3, current datetime (msecs): %4")
                     .arg(getName())
                     .arg(getIndex())
                     .arg(getId().toString())
                     .arg(QDateTime::currentDateTime().toMSecsSinceEpoch())
                     );
                     */
    if(!this->getAutodelete()) this->start();
    //this->start();
    //qDebug() << QString("Current MSecsSinceEpoch: %1").arg(QDateTime::currentDateTime().toMSecsSinceEpoch());
}

void Reminder::stopSound()
{
    emit this->stopNotify();
}

void Reminder::setState(Reminder::ReminderState s)
{
    state = s;
    if(s == Reminder::Runned) start();
    else if(s == Reminder::Stopped) stop();
}

void Reminder::setItem(ReminderItem *i)
{
    item = i;
    item->setText(this->getName());
}

void Reminder::setIcon()
{
    if(item){
        switch(state){
        case Reminder::Postponed:
        case Reminder::Runned:
            item->setIcon(QIcon(":/Icons/icons/AlertReminder.png"));
            break;
        case Reminder::Stopped:
            item->setIcon(QIcon(":/Icons/icons/RefreshReminder.png"));
            break;
        default:
            break;
        }
    }
}

void Reminder::setDays(const bool _days[7])
{
    for(int i=0; i < 7; i++){
        days[i] = _days[i];
    }
}

QDateTime Reminder::dayOfNextMonths(QDateTime previous, int months) const
{
    int w = weekOfMonth(getStart());
    QDateTime nextm = previous;
    do {
        nextm = nextm.addDays(7);
    }
    while( (previous.date().addMonths(months).month() - nextm.date().month() ) != 0);
    while(weekOfMonth(nextm) != w){
        nextm = nextm.addDays(7);
    }
    return nextm;
}

QDateTime Reminder::dayOfPreviousMonths(QDateTime current, int months) const
{
    int w = weekOfMonth(getStart());
    QDateTime prevm = current;
    do {
        prevm = prevm.addDays(-7);
    }
    while( (current.date().addMonths(-months).month() - prevm.date().month() ) != 0);
    while(weekOfMonth(prevm) != w){
        prevm = prevm.addDays(-7);
    }
    return prevm;
}

int Reminder::weekOfMonth(const QDateTime& dt) const
{
    int w;
    int day = dt.date().day();
    if(day <= 7) w = 1;
    else if(day <= 14) w = 2;
    else if(day <= 21) w = 3;
    else if(day <= 28) {
        QDateTime next(dt.addDays(7));
        if(next.date().month() == dt.date().month()){
            w = 4;
        }
        else w = 5;
    }
    else w = 5;
    return w;
}

QString Reminder::missedReminders(const QDateTime& last_call) const
{
    int limit = 30; //secs
    int missed_limit = 1;
    QString missed;
    switch(this->type){
    case Reminder::Once : {
        if(last_call.isNull()){
            if(QDateTime::currentDateTime().secsTo(this->getStart()) < 0){
                missed.append(tr("Missed reminder: %1\n").arg(this->getStart().toString("dd.MM.yyyy hh:mm:ss")));
            }
        }
        break;
    }
    case Reminder::Minutely : {
        //qDebug() << "Check minutely reminder: " << getName();
        if(!this->getNextRun().isNull() && (this->getMinutes() > 0) && !last_call.isNull()){
            //qDebug() << "Checking continue ... Last call: " << last_call;
            QDateTime missed_call = this->getNextRun().addSecs(-(getMinutes()*60));
            while(abs(last_call.secsTo(missed_call)) > limit && last_call.secsTo(missed_call) > 0)
            {
                //qDebug() << "There are missed events!";
                missed.append(tr("Missed reminder: %1\n").arg(missed_call.toString("dd.MM.yyyy hh:mm:ss")));
                missed_call = missed_call.addSecs(-(getMinutes()*60));
                if(++missed_limit >= 10){
                    missed.append(tr("And more since %1").arg(last_call.toString("dd.MM.yyyy hh:mm:ss")));
                    break;
                }
            }
        }
        break;
    }
    case Reminder::Hourly : {
        if(!getNextRun().isNull() && (this->getHours() > 0)  && !last_call.isNull()){
            QDateTime missed_call = this->getNextRun().addSecs(-(getHours()*60*60));
            while(abs(last_call.secsTo(missed_call)) > limit && last_call.secsTo(missed_call) > 0)
            {
                missed.append(tr("Missed reminder: %1\n").arg(missed_call.toString("dd.MM.yyyy hh:mm:ss")));
                missed_call = missed_call.addSecs(-(getHours()*60*60));
                if(++missed_limit >= 10){
                    missed.append(tr("And more since %1").arg(last_call.toString("dd.MM.yyyy hh:mm:ss")));
                    break;
                }
            }
        }
        break;
    }
    case Reminder::Daily : {
        if(!getNextRun().isNull() && !last_call.isNull()){
            QDateTime missed_call = this->getNextRun().addDays(-1);
            while(abs(last_call.secsTo(missed_call)) > limit && last_call.secsTo(missed_call) > 0){
                if(days[missed_call.date().dayOfWeek()-1]){
                    missed.append(tr("Missed reminder: %1\n").arg(missed_call.toString("dd.MM.yyyy hh:mm:ss")));
                    if(++missed_limit >= 10){
                        missed.append(tr("And more since %1").arg(last_call.toString("dd.MM.yyyy hh:mm:ss")));
                        break;
                    }
                }
                missed_call = missed_call.addDays(-1);
            }
        }
        break;
    }
    case Reminder::Monthly : {
        if(this->isRunned() && !last_call.isNull()){
            QDateTime missed_call = this->getNextRun().addMonths(-1);
            while(abs(last_call.secsTo(missed_call)) > limit && last_call.secsTo(missed_call) > 0){
                missed.append(tr("Missed reminder: %1\n").arg(missed_call.toString("dd.MM.yyyy hh:mm:ss")));
                missed_call = missed_call.addMonths(-1);
                if(++missed_limit >= 10){
                    missed.append(tr("And more since %1").arg(last_call.toString("dd.MM.yyyy hh:mm:ss")));
                    break;
                }
            }
        }
        break;
    }
    case Reminder::MonthlyByDay : {
        if(this->isRunned() && !last_call.isNull()){
            QDateTime missed_call = this->dayOfPreviousMonths(this->getNextRun());
            while(abs(last_call.secsTo(missed_call)) > limit && last_call.secsTo(missed_call) > 0){
                missed.append(tr("Missed reminder: %1\n").arg(missed_call.toString("dd.MM.yyyy hh:mm:ss")));
                missed_call = dayOfPreviousMonths(missed_call);
                if(++missed_limit >= 10){
                    missed.append(tr("And more since %1").arg(last_call.toString("dd.MM.yyyy hh:mm:ss")));
                    break;
                }
            }
        }
        break;
    }
    case Reminder::Yearly : {
        if(this->isRunned() && !last_call.isNull()){
            QDateTime missed_call = this->getNextRun().addMonths(-12);
            while(abs(last_call.secsTo(missed_call)) > limit && last_call.secsTo(missed_call) > 0){
                missed.append(tr("Missed reminder: %1\n").arg(missed_call.toString("dd.MM.yyyy hh:mm:ss")));
                missed_call = missed_call.addMonths(-12);
            }
        }
        break;
    }
    case Reminder::YearlyByDay : {
        if(this->isRunned() && !last_call.isNull()){
            QDateTime missed_call = this->dayOfPreviousMonths(this->getNextRun(), 12);
            while(abs(last_call.secsTo(missed_call)) > limit && last_call.secsTo(missed_call) > 0){
                missed.append(tr("Missed reminder: %1\n").arg(missed_call.toString("dd.MM.yyyy hh:mm:ss")));
                missed_call = dayOfPreviousMonths(missed_call, 12);
            }
        }
        break;
    }
    case Reminder::Timeout : {
        if(!this->getNextRun().isNull() && (this->getTimeout() > 0) && !last_call.isNull()){
            QDateTime missed_call = this->getNextRun().addSecs(-(getTimeout()));
            while(abs(last_call.secsTo(missed_call)) > limit && last_call.secsTo(missed_call) > 0)
            {
                missed.append(tr("Missed reminder: %1\n").arg(missed_call.toString("dd.MM.yyyy hh:mm:ss")));
                missed_call = missed_call.addSecs(-(getTimeout()));
                if(++missed_limit >= 10){
                    missed.append(tr("And more since %1").arg(last_call.toString("dd.MM.yyyy hh:mm:ss")));
                    break;
                }
            }
        }
        break;

    }
    default: break;
    }
    return missed;
}

QString Reminder::getStrState() const
{
    QString state;
    switch(getState())
    {
    case Reminder::Runned:
        state = "Runned";
        break;
    case Reminder::Postponed:
        state = "Postponed";
        break;
    case Reminder::Stopped:
        state = "Stopped";
        break;
    default:
        state = "Unknown";
    }
    return state;
}

void Reminder::suspend()
{
    if(timer){
        if(timer->isActive()){
            timer->stop();
        }
    }
}
